/*
 * Copyright (c) 2012 OptiCare Systems Ltd
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 *
 * Authors: Kevin Martin (kev82@khn.org.uk)
 */
#include <curl/curl.h>
#include <string>
#include <stdexcept>
#include <cstdlib>
#include <sstream>
#include <cassert>
#include <vector>
#include <cstring>

#include "connect.hpp"

//Takes a map and converts to a get/post string
std::string varString(const MSS &m, bool encode = true) {
	if(m.empty()) return "";

	CURL *c = curl_easy_init();

	std::ostringstream oss;

	MSS::const_iterator i = m.begin();
	oss << i->first << "=";
	if(encode) {
		char *t2 = curl_easy_escape(c, i->second.c_str(), 0);
		oss << t2;
		curl_free(t2);
	} else {
		oss << i->second;
	}
	++i;
		
	while(i != m.end()) {
		oss << "&" << i->first << "=";
		if(encode) {
			char *t2 = curl_easy_escape(c, i->second.c_str(), 0);
			oss << t2;
			curl_free(t2);
		} else {
			oss << i->second;
		}
		++i;
	}

	curl_easy_cleanup(c);

	return oss.str();
}

//We pass this to curl to grab the whole output
static size_t appendVecChar(void *ptr, size_t n, size_t nmemb, void *str) {
    std::vector<char> &x = *reinterpret_cast<std::vector<char> *>(str);

    size_t bytes = nmemb * n;
    char *data = reinterpret_cast<char *>(ptr);
//    x += std::string(data, data+bytes);
	for(int i=0;i!=bytes;++i) {
		x.push_back(data[i]);
	}

    return bytes;
}

static size_t readVecChar(void *ptr, size_t size, size_t nmemb, void *userdata) {
	std::pair<std::vector<char>, int> &x =
	 *reinterpret_cast<std::pair<std::vector<char>, int> *>(userdata);

	const char *start = &(x.first[0]) + x.second;
	int bytesLeft = x.first.size() - x.second;
	int curlmaxbytes = nmemb*size;
	int bytesCopied = std::min(curlmaxbytes, bytesLeft);

	memcpy(ptr, start, bytesCopied);

	x.second += bytesCopied;
	return bytesCopied;
}

std::vector<char> CurlConnect(const ConnectionOptions &co) {
	curl_global_init(CURL_GLOBAL_ALL);

    CURL *curl;
    CURLcode res;

    curl = curl_easy_init();
    if(!curl) {
        throw std::runtime_error("failed to create curl easy session");
    }

	if(co.beVerbose) {
		curl_easy_setopt(curl, CURLOPT_VERBOSE, 1);
	}

	if(co.addHeaders) {
		curl_easy_setopt(curl, CURLOPT_HEADER, 1);
	}

	if(co.noBody) {
		curl_easy_setopt(curl, CURLOPT_NOBODY, 1);
	}

	{
		std::string url = co.URL;
		if(!co.getdatamap.empty()) {
			url += "?";
			url += varString(co.getdatamap);
		}
		curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
	}

	if(co.dopost) {
		std::string x = varString(co.postdatamap);
        curl_easy_setopt(curl, CURLOPT_COPYPOSTFIELDS, x.c_str());
	}

	std::pair<std::vector<char>, int> readdata(co.putdata, 0);
	if(co.doput) {
		curl_easy_setopt(curl, CURLOPT_UPLOAD, 1);
		curl_easy_setopt(curl, CURLOPT_READDATA, (void *)&readdata);
		curl_easy_setopt(curl, CURLOPT_READFUNCTION, readVecChar);
		curl_easy_setopt(curl, CURLOPT_INFILESIZE, co.putdata.size());
	}

	if(co.dodel) {
		curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "DELETE");
	}

    curl_slist *header_slist = NULL;
    if(!co.headermap.empty()) {
		for(MSS::const_iterator hi=co.headermap.begin();hi!=co.headermap.end();++hi) {
			std::ostringstream oss;
			oss << hi->first << ": " << hi->second;
			header_slist = curl_slist_append(header_slist, oss.str().c_str());
		}
        curl_easy_setopt(curl, CURLOPT_HTTPHEADER, header_slist);
    }

    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION,
     appendVecChar);
    std::vector<char> output;
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &output);

    res = curl_easy_perform(curl);
    if(header_slist != NULL) curl_slist_free_all(header_slist);

    if(res != 0) {
        throw std::runtime_error("Curl failed");
    }

    curl_easy_cleanup(curl);
	curl_global_cleanup();

    return output;
}

